﻿using System;
using System.IO;
using System.Linq;

using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Import.OBJLoader;
using RayDen.Library.Entity;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Renderer;
using RayDen.Library.Entity.Scene;
using RayDen.Library.Entity.SceneGraph;

using RayDen.RayEngine.Corrector.Managers;
using RayDen.RayEngine.Corrector.Renderers;
using RayDen.RayEngine.Corrector.Types;
using RayDen.RayEngine.Engines.PathTracer.Volume;
using RayDen.RayEngine.Managers;
using RayDen.RayEngine.Samplers;
using RayDen.RayEngine.Scene;

namespace RayDen.RayEngine.Corrector
{
    public class CorrectorEngineConfiguration : IRenderConfig
    {
        public IRendererOutputSettings OutputSettings { get; set; }
        public IRendererQualitySettings QualitySettings { get; set; }
        public bool lowLatency = true;
        public bool AreaLightAsMeshLight = true;
        public int RenderThreadsCount { get; set; }

        public FrameConfiguration CurrentFrame;

        private string imagesPath = GlobalConfiguration.Instance.Get<string>("ImageSavePath");
        private string frameName;

        private CorrectorImageFilm film;

        private DependencyManager dependencyManager;
        private SceneBuilder _builder;
        private FrameManager frameManager;
        private DeviceManager deviceManager;
        public ICamera Camera;

        private SceneGeometryInfo scene;
        protected MaterialInfo[] mats;
        protected int currentCamera = 0;
        private DateTime start;
        public RenderingEngines engineType = GlobalConfiguration.Instance.Engine;

        protected IParameterBag config;
        public SceneGraphInfo SceneGraph { get; protected set; }

        private ICorrectorRenderer engine;

        public string SavePath
        {
            get { return imagesPath; }
        }

        public string FrameName
        {
            get { return frameName; }
            protected set { frameName = value; }
        }

        public int Width
        {
            get { return config.Get<int>("ImageWidth"); }
        }
        public int Height
        {
            get { return config.Get<int>("ImageHeight"); }
        }


        public long TotalSamples
        {
            get { return this.deviceManager.RenderThreads.Sum(renderThread => renderThread.samplers.PrimarySpaceSampler.TotalSamples); }
        }

        public int Pass
        {
            get { return this.deviceManager.RenderThreads.Sum(renderThread => renderThread.samplers.PrimarySpaceSampler.GetPass()); }
        }


        protected RayEngineScene Scene
        {
            get { return this.CurrentFrame.Scene; }
            set { this.CurrentFrame.Scene = value; }
        }


        public CorrectorEngineConfiguration()
        {
            this.config = new ConfigurableEntity(GlobalConfiguration.Instance);
            this.dependencyManager = new DependencyManager();
        }
        public virtual void Initialize()
        {

            this.deviceManager = new DeviceManager();
            this.frameManager = new FrameManager(this.OutputSettings, this.QualitySettings, Scene);
            this.deviceManager.Init(lowLatency, Scene);
            //this.frameManager.SetDevices(deviceManager, Film);
            this.film = new CorrectorImageFilm(Width, Height);
            this.engine = new CorrectorRenderer(CurrentFrame, film);
            this.deviceManager.RenderThreads = engine.CreateThreads(this.RenderThreadsCount, deviceManager.IntersectionDevice, frameManager);



            Tracer.TraceLine("Starting Corrector [Renderer type : {0}] ( Preprocessing : {1} )", engineType, false);
            //Tracer.TraceLine("..Sampler Type [{0}]", deviceManager.RenderThreads.First().sampler.GetType().Name);
            Tracer.TraceLine("..Image Film Type [{0}]", film.GetType().Name);
            CommandManager.Instance.OnExecuteCommand += command =>
            {
                if (command.Name == "init.config")
                {

                }

                return new EngineCommandContext() { Success = true };
            };
        }
        public void SaveImage(string fn)
        {
            (this.film).SavePNG(fn, GetStats().Split(';').Select(item => item.Trim()).Union(new[] { string.Format("Engine - {0}", this.engineType), SamplerFactory.Instance.GetSamplerType() }).ToArray());
        }

        public void SaveRawImage(string fn)
        {
            this.film.SaveRaw(fn);
        }

        public void OpenFrame(Library.Entity.Frames.FrameDescription frame)
        {
            this.CurrentFrame = new FrameConfiguration();

            this.config.Merge(frame);
            this.OutputSettings = new OutputSettingsInfo(frame);
            this.QualitySettings = new QualitySettingsInfo(frame);

            this.FrameName = frame.FrameName;

            if (!string.IsNullOrWhiteSpace(frame.WorkingDir))
                Directory.SetCurrentDirectory(frame.WorkingDir);
            /*
            foreach (var frameElement in frame.Elements.Where(frameElement => frameParsers.ContainsKey(frameElement.GetType()))) {
                frameParsers[frameElement.GetType()](frameElement);
            }*/

            if (Scene == null)
            {
                SceneGraphService svc = new SceneGraphService();
                svc.Populate(
                    //new ObjLoader(),
                    new GlobalIndexObjLoader(),
                    new MaterialLoader());

                //svc.OpenFrame(frame);
                _builder = new SceneBuilder() { SGService = svc };
                _builder.Setup();
                this.SceneGraph = _builder.SGService.SceneGraph;
                this.Scene = _builder.OpenFrame(frame, this.AreaLightAsMeshLight);

                this.Scene.DefaultLightGain = config.Get("LightGain", new RgbSpectrum(1.0f));
                this.Scene.MaxPathDepth = config.Get("MaxPathDepth", 10);
                this.Scene.RussianRuletteImportanceCap = config.Get("RRImportanceCap", 0.75f);
                this.Scene.MaxPaths = config.Get("PathBufferSize", 65536);
                this.Scene.ShadowRayCount = config.Get("ShadowRayCount", 1);
                this.Scene.TextureSamplingQuality = this.QualitySettings.TextureSamplingQuality;
                this.Scene.LightSamplingStrategy = this.QualitySettings.LightSamplingStrategy;
                RgbSpectrum.Gamma = true;
                /*
                if (GlobalConfiguration.Instance.SpectralRendering)
                {
                    this.Scene.SpectralMats = new BxDFLibrary(new LambertianBrdf(new[] { 0.5f, 0.5f, 0.5f }));
                    this.Scene.SpectralMats.Populate(_builder.MaterialInfos.ToArray());
                }
                else*/

                {
                    this.Scene.MatLib = this.dependencyManager.GetMaterialPlugin(_builder.MaterialInfos.ToArray());
                    if (_builder.FrameMaterialBuilder != null)
                    {
                        this.Scene.MatLib.Add(_builder.FrameMaterialBuilder.Materials);
                        _builder.FrameMaterialBuilder.CreateRayEngineMaterials(this.Scene);
                    }
                    //if (EnableMaterialOverrides) FrameMaterialBuilder.ApplyMaterialOverrides((this.Scene.MatLib as OptimizedBsdfLibrary).Materials, Scene);
                }


                RenderThreadsCount = config.Get("RenderThreadsCount", 1);
                this.scene = _builder.SceneGeo;

                var sceneBound = new AABB(scene.BoundingSphereCenter - scene.BoundingSphereRadius,
                                          scene.BoundingSphereCenter + scene.BoundingSphereRadius);
                sceneBound.Expand(1f);

                //var viewBound = new AABB(Scene.Camera.Target - 5f, Scene.Camera.Target + 5f);
                var c = new Point(Scene.Camera.Target);
                var bb = new AABB(c - 1f, c + 1f);
                var volumeBound = sceneBound;

                Scene.VolumeIntegrator = new SingleScatteringIntegrator(volumeBound,
                                                                         volumeBound.Size.Length / 10f, //step
                                                                         0.05f, //Probability
                                                                         RgbSpectrum.UnitSpectrum() * 0.05f,  //Inscatering
                                                                         new RgbSpectrum(0.000f)             //Emission

                                                                       );
                new RayEngineMaterialBuilder().BuildMaterials(this.Scene);
            }
            this.CurrentFrame.Width = frame.Get<int>("ImageWidth");
            this.CurrentFrame.Height = frame.Get<int>("ImageHeight");
            this.CurrentFrame.ImageSavePath = this.SavePath;
            this.CurrentFrame.LowLatency = lowLatency;
            this.CurrentFrame.MaxSamplesPerPixel = this.QualitySettings.SamplesPerPixel;

            this.Initialize();
            this.Scene.BuildPrimitives();
        }

        public void Start()
        {
            IsRunning = true;
            start = DateTime.UtcNow;
            this.RenderThreadsCount = this.deviceManager.RenderThreads.Length;
            this.deviceManager.Start();
        }

        public bool IsRunning
        {
            get;
            set;
        }

        public void Interrupt()
        {
            this.deviceManager.Interrupt();
        }

        public void ConsumeJob(IRenderJob job, Action<IRenderJobResult> resultCallback)
        {
            throw new NotImplementedException();
        }

        public string GetStats()
        {
            long samps = 0;
            foreach (var renderThread in deviceManager.RenderThreads)
            {
                samps += renderThread.samplers.PrimarySpaceSampler.TotalSamples;
            }
            var totalSamples = samps;
            //this.deviceManager.RenderThreads.Sum(renderThread => renderThread.sampler.TotalSamples);

            var samplesPerSec = totalSamples / (float)(DateTime.UtcNow - start).TotalSeconds;

            var rays = (this.deviceManager.IntersectionDevice.TotalRays / (DateTime.UtcNow - start).TotalSeconds) / 1000000d;
            return string.Format("{4:F5} MRays/sec {0:F5} MSamples/sec ; {1} Elapsed ; {2} Samples Per Pixel ; {3} ; {5};",
                //{0:F5} MRays/sec  this.IntersectionDevice.GetPerformance() / 1000000d, 
                samplesPerSec / 1000000d, DateTime.UtcNow - start, Pass, engine.GetEngineStats(), rays, film.GetStats()
                //_renderThreads[0].sampler.GetPass(), 
                //Engine.GetStats()
                );
        }

        public void Dispose()
        {
            if (deviceManager != null)
            {
                deviceManager.Dispose();
            }

        }

        public void RunCommand(int index)
        {

        }

        public IImagePlaneComponent GetImagePlane()
        {
            return this.film;
        }
    }
}
